package com.example.yingjiguanli_demo.handler;

import com.example.yingjiguanli_demo.utils.GameConstants;
import com.example.yingjiguanli_demo.service.IMatchesService;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import static com.example.yingjiguanli_demo.utils.GameConstants.*;

/**
 * WebSocket处理器，用于处理游戏中的匹配和比赛逻辑。
 * 该处理器确保在高并发环境下的线程安全和高效性。
 */
@Slf4j
public class GameWebSocketHandler extends TextWebSocketHandler {

    // 存储用户的匹配状态（是否准备匹配）
    private static final ConcurrentHashMap<Long, Boolean> userMatchStatus = new ConcurrentHashMap<>();

    // 存储用户的WebSocket会话，确保可以发送消息给对应的用户
    private static final ConcurrentHashMap<Long, WebSocketSession> userSessions = new ConcurrentHashMap<>();

    // 存储等待匹配的用户及其加入等待队列的时间
    private static final ConcurrentHashMap<Long, Long> waitingUsers = new ConcurrentHashMap<>();

    // 存储用户的分数，用于比赛结束时计算结果
    private static final ConcurrentHashMap<Long, Integer> userScores = new ConcurrentHashMap<>();

    // 定时清理过期用户的执行器，避免等待队列无限增长
    private static final ScheduledExecutorService cleanupExecutor = Executors.newSingleThreadScheduledExecutor();

    // 锁用于保护匹配过程中的数据一致性，避免竞态条件
    private final Lock matchLock = new ReentrantLock();

    // JSON处理工具，用于序列化和反序列化消息
    private final ObjectMapper objectMapper = new ObjectMapper();

    // 匹配服务，用于异步记录比赛结果
    private final IMatchesService matchService;

    /**
     * 构造器，注入匹配服务
     *
     * @param matchService 匹配服务接口
     */
    public GameWebSocketHandler(IMatchesService matchService) {
        this.matchService = matchService;
    }

    /**
     * 静态块，启动定时任务定期清理等待队列中过期的用户。
     * 过期时间和清理间隔由GameConstants定义。
     */
    static {
        cleanupExecutor.scheduleAtFixedRate(() -> {
            long currentTime = System.currentTimeMillis();
            List<Long> expiredUsers = new ArrayList<>();

            // 遍历等待队列，查找超过超时时间的用户
            for (Map.Entry<Long, Long> entry : waitingUsers.entrySet()) {
                Long userId = entry.getKey();
                long joinTime = entry.getValue();

                // 判断用户是否超时
                if (currentTime - joinTime > MATCH_TIMEOUT_MS) {
                    WebSocketSession session = userSessions.get(userId);
                    if (session == null || !session.isOpen()) {
                        expiredUsers.add(userId);
                        log.info("用户 {} 超过 {} 毫秒未匹配，加入清理列表", userId, MATCH_TIMEOUT_MS);
                    }
                }
            }

            // 清理过期用户
            for (Long userId : expiredUsers) {
                userSessions.remove(userId);
                waitingUsers.remove(userId);
                userMatchStatus.remove(userId);
                userScores.remove(userId);
                log.info("清理过期用户：{}", userId);
            }
        }, 0, CLEANUP_INTERVAL_MS, TimeUnit.MILLISECONDS);
    }

    /**
     * WebSocket连接建立时的回调方法。
     * 将用户会话存储到userSessions，并加入等待队列。
     *
     * @param session 当前WebSocket会话
     * @throws Exception 可能抛出的异常
     */
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        // 从会话属性中获取用户ID
        Long userId = (Long) session.getAttributes().get("userId");
        if (userId != null) {
            // 存储用户的会话
            userSessions.put(userId, session);

            // 如果用户之前在等待队列中，重新加入等待队列
            if (userMatchStatus.getOrDefault(userId, USER_NOT_MATCH_READY)) {
                waitingUsers.put(userId, System.currentTimeMillis());
                log.info("用户 {} 重新连接，加入等待队列", userId);
                handleMatchAction(session, userId);
            }

            // 如果用户没有匹配准备，则默认加入等待队列
            if (!waitingUsers.containsKey(userId) && !userMatchStatus.containsKey(userId)) {
                waitingUsers.put(userId, System.currentTimeMillis());
                userMatchStatus.put(userId, USER_NOT_MATCH_READY);
                log.info("用户 {} 连接成功，加入等待队列", userId);
            }

            log.info("用户连接成功，用户ID: {}，当前在线用户数: {}", userId, userSessions.size());
        } else {
            // 如果用户ID无效，记录警告并关闭会话
            log.warn(INVALID_USER_ID_MSG);
            session.close(BAD_DATA_STATUS);
        }
    }

    /**
     * 处理客户端发送的文本消息。
     * 根据消息的action字段，调用相应的处理方法。
     *
     * @param session 当前WebSocket会话
     * @param message 客户端发送的消息
     * @throws Exception 可能抛出的异常
     */
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        try {
            String payload = message.getPayload();
            log.info("接收到消息: {}", payload);

            // 从会话属性中获取用户ID
            Long userId = (Long) session.getAttributes().get("userId");
            if (userId != null) {
                // 解析JSON消息
                Map<String, Object> data = objectMapper.readValue(payload, Map.class);
                String action = (String) data.get("action");

                // 根据action类型处理不同的请求
                if (ACTION_MATCH.equals(action)) {
                    handleMatchAction(session, userId); // 处理匹配请求
                } else if (ACTION_FINISH.equals(action)) {
                    handleFinishAction(session, data, userId); // 处理比赛结束
                } else {
                    log.warn(UNKNOWN_ACTION_TYPE_MSG, action);
                    sendErrorMessage(session);
                }
            } else {
                log.warn("无法获取用户ID");
                sendErrorMessage(session);
            }
        } catch (Exception e) {
            log.error("处理消息时出现异常", e);
            sendErrorMessage(session);
        }
    }

    /**
     * WebSocket连接关闭时的回调方法。
     * 清理用户的会话和相关数据。
     *
     * @param session 当前WebSocket会话
     * @param status 关闭状态
     * @throws Exception 可能抛出的异常
     */
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        // 从会话属性中获取用户ID
        Long userId = (Long) session.getAttributes().get("userId");
        if (userId != null) {
            // 移除用户的会话和相关数据
            userSessions.remove(userId);
            waitingUsers.remove(userId);
            userMatchStatus.remove(userId);
            userScores.remove(userId);
            log.info(USER_DISCONNECTED_MSG, userId, userSessions.size());
        }
    }

    /**
     * 查找一个可用的对手进行匹配。
     * 使用锁确保在高并发环境下的线程安全。
     *
     * @param userId 当前请求匹配的用户ID
     * @return 可用的对手用户ID，如果没有找到则返回null
     */
    private Long findOpponentId(Long userId) {
        // 加锁，确保匹配过程的原子性
        matchLock.lock();
        try {
            List<Long> waitingList = new ArrayList<>(waitingUsers.keySet());

            // 移除自己避免被匹配
            waitingList.remove(userId);

            // 如果等待队列为空，返回null
            if (waitingList.isEmpty()) {
                return null;
            }

            Random random = new Random();
            Long opponentId = null;

            // 随机选择一个对手，确保对手不是自己且处于准备匹配的状态
            while (opponentId == null && !waitingList.isEmpty()) {
                int randomIndex = random.nextInt(waitingList.size());
                Long potentialOpponentId = waitingList.get(randomIndex);

                // 检查潜在对手是否有效
                if (userMatchStatus.getOrDefault(potentialOpponentId, USER_NOT_MATCH_READY)) {
                    opponentId = potentialOpponentId;
                } else {
                    // 移除无效的潜在对手
                    waitingList.remove(randomIndex);
                }
            }

            if (opponentId != null) {
                // 移除对手从等待队列中，并更新其匹配状态
                waitingUsers.remove(opponentId);
                userMatchStatus.put(opponentId, USER_NOT_MATCH_READY);
            }

            return opponentId;
        } finally {
            // 释放锁
            matchLock.unlock();
        }
    }

    /**
     * 处理匹配请求。
     * 尝试找到一个对手进行匹配，如果找到则通知双方匹配成功，否则让用户等待。
     *
     * @param session 当前WebSocket会话
     * @param userId  当前请求匹配的用户ID
     * @throws Exception 可能抛出的异常
     */
    private void handleMatchAction(WebSocketSession session, Long userId) throws Exception {
        // 将用户状态设置为准备匹配
        userMatchStatus.put(userId, USER_MATCH_READY);

        // 查找一个可用的对手
        Long opponentId = findOpponentId(userId);

        if (opponentId != null) {
            // 获取对手的会话
            WebSocketSession opponentSession = userSessions.get(opponentId);
            if (opponentSession != null && opponentSession.isOpen()) {
                // 发送匹配成功消息给当前用户和对手
                sendMatchSuccessMessage(session, userId, opponentSession, opponentId);
                sendMatchSuccessMessage(opponentSession, opponentId, session, userId);
                log.info(MATCH_SUCCESS_LOG, userId, opponentId);

                // **关键修复点**：将当前用户的匹配状态也设置为不准备匹配，防止再次匹配
                userMatchStatus.put(userId, USER_NOT_MATCH_READY);
            } else {
                // 如果对手的会话无效，记录警告并重新加入等待队列
                log.warn("对手会话未找到或未打开，用户ID: {}", opponentId);
                userMatchStatus.put(userId, USER_NOT_MATCH_READY);
                waitingUsers.put(userId, System.currentTimeMillis());
                sendWaitingMessage(session);
            }
        } else {
            // 如果没有找到对手，确保用户已在等待队列中
            if (!waitingUsers.containsKey(userId)) {
                waitingUsers.put(userId, System.currentTimeMillis());
                log.info(WAITING_LOG, userId);
            }

            // 发送等待消息给用户
            sendWaitingMessage(session);
        }
    }

    /**
     * 发送等待匹配的消息给用户。
     *
     * @param session 当前WebSocket会话
     * @throws Exception 可能抛出的异常
     */
    private void sendWaitingMessage(WebSocketSession session) throws Exception {
        Map<String, String> waitingMessage = new HashMap<>();
        waitingMessage.put("status", STATUS_WAITING);
        waitingMessage.put("message", STATUS_MESSAGE_WAITING);
        session.sendMessage(new TextMessage(objectMapper.writeValueAsString(waitingMessage)));
    }

    /**
     * 发送匹配成功的消息给用户。
     *
     * @param session        当前用户的WebSocket会话
     * @param userId         当前用户ID
     * @param opponentSession 对手的WebSocket会话
     * @param opponentId     对手的用户ID
     * @throws Exception 可能抛出的异常
     */
    private void sendMatchSuccessMessage(WebSocketSession session, Long userId, WebSocketSession opponentSession, Long opponentId) throws Exception {
        Map<String, String> successMessage = new HashMap<>();
        successMessage.put("status", STATUS_MATCHED);
        successMessage.put("opponentId", opponentId.toString());
        session.sendMessage(new TextMessage(objectMapper.writeValueAsString(successMessage)));
    }

    /**
     * 处理比赛结束请求。
     * 计算比赛结果，并将结果发送给双方用户。
     *
     * @param session 当前WebSocket会话
     * @param data    客户端发送的比赛结束数据
     * @param userId  当前用户ID
     * @throws Exception 可能抛出的异常
     */
    private void handleFinishAction(WebSocketSession session, Map<String, Object> data, Long userId) throws Exception {
        Long opponentId = Long.parseLong(data.get("opponentId").toString());
        Integer userScore = Integer.parseInt(data.get("userScore").toString());
        Integer subjectId = Integer.parseInt(data.get("subjectId").toString());

        // 将用户分数存储
        userScores.put(userId, userScore);

        // 获取对手的分数
        Integer opponentScore = userScores.getOrDefault(opponentId, null);
        if (opponentScore == null) {
            // 如果对手尚未提交分数，记录日志并等待
            log.info("用户 {} 已提交分数，等待对手 {} 提交分数", userId, opponentId);
            return;
        }

        // 计算比赛结果
        String userResult = determineResult(userScore, opponentScore);
        String opponentResult = determineResult(opponentScore, userScore);

        // 创建并发送结果消息给当前用户
        Map<String, Object> userMessage = createResultMessage(userScore, opponentScore, userResult);
        session.sendMessage(new TextMessage(objectMapper.writeValueAsString(userMessage)));

        // 获取对手的会话并发送结果消息
        WebSocketSession opponentSession = userSessions.get(opponentId);
        if (opponentSession != null && opponentSession.isOpen()) {
            Map<String, Object> opponentMessage = createResultMessage(opponentScore, userScore, opponentResult);
            opponentSession.sendMessage(new TextMessage(objectMapper.writeValueAsString(opponentMessage)));
        }

        // 异步记录比赛结果
        recordMatchResultAsync(userId, opponentId, userScore, opponentScore, subjectId);
        log.info(MATCH_RESULT_LOG, userId, userScore, opponentId, opponentScore, userResult);

        // 清理比赛相关数据
        userScores.remove(userId);
        userScores.remove(opponentId);

        // 重置用户的匹配状态
        userMatchStatus.put(userId, USER_NOT_MATCH_READY);
        userMatchStatus.put(opponentId, USER_NOT_MATCH_READY);
    }

    /**
     * 创建比赛结果消息。
     *
     * @param userScore     当前用户的分数
     * @param opponentScore 对手的分数
     * @param result        当前用户的比赛结果（胜利、失败、平局）
     * @return 结果消息的Map表示
     */
    private Map<String, Object> createResultMessage(Integer userScore, Integer opponentScore, String result) {
        Map<String, Object> resultMessage = new HashMap<>();
        resultMessage.put("action", ACTION_RESULT);
        resultMessage.put("userScore", userScore);
        resultMessage.put("opponentScore", opponentScore);
        resultMessage.put("result", result);
        return resultMessage;
    }

    /**
     * 判断比赛结果。
     *
     * @param userScore     当前用户的分数
     * @param opponentScore 对手的分数
     * @return 比赛结果字符串（"胜利"、"失败"、"平局"）
     */
    private String determineResult(Integer userScore, Integer opponentScore) {
        if (userScore > opponentScore) {
            return MATCH_RESULT_WIN;
        } else if (userScore < opponentScore) {
            return MATCH_RESULT_LOSE;
        } else {
            return MATCH_RESULT_TIE;
        }
    }

    /**
     * 异步记录比赛结果到数据库或其他存储介质。
     *
     * @param userId         当前用户ID
     * @param opponentId     对手用户ID
     * @param userScore      当前用户的分数
     * @param opponentScore  对手的分数
     * @param subjectId      主题ID（比赛类别等）
     */
    @Async
    void recordMatchResultAsync(Long userId, Long opponentId, Integer userScore, Integer opponentScore, Integer subjectId) {
        try {
            matchService.recordMatchResult(userId.intValue(), opponentId.intValue(), userScore, opponentScore, subjectId);
            log.info("异步记录比赛结果：用户ID={} 分数={}，对手ID={} 分数={}，主题ID={}",
                    userId, userScore, opponentId, opponentScore, subjectId);
        } catch (Exception e) {
            log.error("异步记录比赛结果时出错", e);
        }
    }

    /**
     * 发送错误消息给客户端。
     *
     * @param session 当前WebSocket会话
     * @throws Exception 可能抛出的异常
     */
    private void sendErrorMessage(WebSocketSession session) throws Exception {
        Map<String, String> error = new HashMap<>();
        error.put("status", "error");
        error.put("message", "处理消息时出错");
        session.sendMessage(new TextMessage(objectMapper.writeValueAsString(error)));
    }
}
